//! Integration test for rendering 2D images with both VM and JIT evaluators
use fidget::{
    Context,
    eval::{Function, MathFunction},
    gui::View2,
    raster::ImageRenderConfig,
    render::ImageSize,
    shape::{Shape, ShapeVars},
    var::Var,
};
use nalgebra::Point2;

const HI: &str =
    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../models/hi.vm"));
const QUARTER: &str =
    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../models/quarter.vm"));

#[derive(Default)]
struct Cfg {
    vars: ShapeVars<f32>,
    view: View2,
    wide: bool,
}

impl Cfg {
    fn test<F: Function>(&self, shape: Shape<F>, expected: &'static str) {
        self.test_with_mat(shape, nalgebra::Matrix3::identity(), expected);
    }

    fn test_with_mat<F: Function>(
        &self,
        shape: Shape<F>,
        world_to_model: nalgebra::Matrix3<f32>,
        expected: &'static str,
    ) {
        let width = if self.wide { 64 } else { 32 };
        let cfg = ImageRenderConfig {
            image_size: ImageSize::new(width, 32),
            world_to_model: world_to_model * self.view.world_to_model(),
            ..Default::default()
        };
        let out = cfg
            .run_with_vars(shape, &self.vars)
            .expect("rendering should not be cancelled");
        let mut img_str = String::new();
        for (i, b) in out.iter().enumerate() {
            if i % width as usize == 0 {
                img_str += "\n            ";
            }
            img_str.push(if b.inside() { '#' } else { '.' });
        }
        if img_str != expected {
            println!("image mismatch detected!");
            println!("Expected:\n{expected}\nGot:\n{img_str}");
            println!("Diff:");
            for (a, b) in img_str.chars().zip(expected.chars()) {
                print!("{}", if a != b { '!' } else { a });
            }
            panic!("image mismatch");
        }
    }
}

fn check_hi<F: Function + MathFunction>() {
    let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
    let shape = Shape::<F>::new(&ctx, root).unwrap();
    const EXPECTED: &str = "
            .................#..............
            .................#..............
            .................#..............
            .................#..........##..
            .................#..........##..
            .................#..............
            .................#..............
            .................######.....##..
            .................###..##....##..
            .................##....##...##..
            .................#......#...##..
            .................#......#...##..
            .................#......#...##..
            .................#......#...##..
            .................#......#...##..
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................";
    Cfg::default().test(shape, EXPECTED);
}

fn check_hi_wide<F: Function + MathFunction>() {
    let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
    let shape = Shape::<F>::new(&ctx, root).unwrap();
    const EXPECTED: &str = "
            .................................#..............................
            .................................#..............................
            .................................#..............................
            .................................#..........##..................
            .................................#..........##..................
            .................................#..............................
            .................................#..............................
            .................................######.....##..................
            .................................###..##....##..................
            .................................##....##...##..................
            .................................#......#...##..................
            .................................#......#...##..................
            .................................#......#...##..................
            .................................#......#...##..................
            .................................#......#...##..................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................
            ................................................................";
    Cfg {
        wide: true,
        ..Default::default()
    }
    .test(shape, EXPECTED);
}

fn check_hi_transformed<F: Function + MathFunction>() {
    let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
    let shape = Shape::<F>::new(&ctx, root).unwrap();
    let mut mat = nalgebra::Matrix3::<f32>::identity();
    mat.prepend_translation_mut(&nalgebra::Vector2::new(0.5, 0.5));
    mat.prepend_scaling_mut(0.5);
    const EXPECTED: &str = "
            .###............................
            .###............................
            .###............................
            .###............................
            .###............................
            .###............................
            .###............................
            .###....................###.....
            .###...................#####....
            .###...................#####....
            .###...................####.....
            .###............................
            .###............................
            .###............................
            .###..######............###.....
            .#############..........###.....
            .###############........###.....
            .######....#####........###.....
            .#####.......####.......###.....
            .####.........###.......###.....
            .###..........####......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            ................................";
    Cfg::default().test_with_mat(shape, mat, EXPECTED);
}

fn check_hi_bounded<F: Function + MathFunction>() {
    let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
    let shape = Shape::<F>::new(&ctx, root).unwrap();
    const EXPECTED: &str = "
            .###............................
            .###............................
            .###............................
            .###............................
            .###............................
            .###............................
            .###............................
            .###....................###.....
            .###...................#####....
            .###...................#####....
            .###...................####.....
            .###............................
            .###............................
            .###............................
            .###..######............###.....
            .#############..........###.....
            .###############........###.....
            .######....#####........###.....
            .#####.......####.......###.....
            .####.........###.......###.....
            .###..........####......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            .###...........###......###.....
            ................................";
    let view =
        View2::from_center_and_scale(nalgebra::Vector2::new(0.5, 0.5), 0.5);
    Cfg {
        view,
        ..Default::default()
    }
    .test(shape, EXPECTED);
}

fn check_quarter<F: Function + MathFunction>() {
    let (ctx, root) = Context::from_text(QUARTER.as_bytes()).unwrap();
    let shape = Shape::<F>::new(&ctx, root).unwrap();
    const EXPECTED: &str = "
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            .....###########................
            .....###########................
            ......##########................
            ......##########................
            ......##########................
            .......#########................
            ........########................
            .........#######................
            ..........######................
            ...........#####................
            ..............##................
            ................................
            ................................
            ................................
            ................................
            ................................";
    Cfg::default().test(shape, EXPECTED);
}

fn check_circle_var<F: Function + MathFunction>() {
    let mut ctx = Context::new();
    let x = ctx.x();
    let y = ctx.y();
    let x2 = ctx.square(x).unwrap();
    let y2 = ctx.square(y).unwrap();
    let r2 = ctx.add(x2, y2).unwrap();
    let r = ctx.sqrt(r2).unwrap();
    let v = Var::new();
    let c = ctx.var(v);
    let root = ctx.sub(r, c).unwrap();
    let shape = Shape::<F>::new(&ctx, root).unwrap();
    const EXPECTED_075: &str = "
            ................................
            ................................
            ................................
            ................................
            ............#########...........
            ..........#############.........
            .........###############........
            ........#################.......
            .......###################......
            ......#####################.....
            ......#####################.....
            .....#######################....
            .....#######################....
            .....#######################....
            .....#######################....
            .....#######################....
            .....#######################....
            .....#######################....
            .....#######################....
            .....#######################....
            ......#####################.....
            ......#####################.....
            .......###################......
            ........#################.......
            .........###############........
            ..........#############.........
            ............#########...........
            ................................
            ................................
            ................................
            ................................
            ................................";
    let mut vars = ShapeVars::new();
    vars.insert(v.index().unwrap(), 0.75);
    Cfg {
        vars,
        ..Default::default()
    }
    .test(shape.clone(), EXPECTED_075);

    const EXPECTED_05: &str = "
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            .............#######............
            ...........###########..........
            ..........#############.........
            ..........#############.........
            .........###############........
            .........###############........
            .........###############........
            .........###############........
            .........###############........
            .........###############........
            .........###############........
            ..........#############.........
            ..........#############.........
            ...........###########..........
            .............#######............
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................
            ................................";
    let mut vars = ShapeVars::new();
    vars.insert(v.index().unwrap(), 0.5);
    Cfg {
        vars,
        ..Default::default()
    }
    .test(shape, EXPECTED_05);
}

fn check_neg_infinity<F: Function + MathFunction>() {
    let mut ctx = Context::new();
    let root = ctx.constant(-f64::INFINITY);
    let shape = Shape::<F>::new(&ctx, root).unwrap();

    let cfg = ImageRenderConfig {
        image_size: ImageSize::new(256, 256),
        pixel_perfect: true,
        threads: None,
        ..Default::default()
    };
    let out = cfg.run(shape).unwrap();
    assert!(out.into_iter().all(|i| i.inside()));
}

macro_rules! render_tests {
    ($i:ident, $ty:ty) => {
        mod $i {
            #[test]
            fn render_hi() {
                super::check_hi::<$ty>();
            }
            #[test]
            fn render_hi_wide() {
                super::check_hi_wide::<$ty>();
            }
            #[test]
            fn render_hi_transformed() {
                super::check_hi_transformed::<$ty>();
            }
            #[test]
            fn render_hi_bounded() {
                super::check_hi_bounded::<$ty>();
            }
            #[test]
            fn render_quarter() {
                super::check_quarter::<$ty>();
            }
            #[test]
            fn render_circle_var() {
                super::check_circle_var::<$ty>();
            }
            #[test]
            fn render_neg_infinity() {
                super::check_neg_infinity::<$ty>();
            }
        }
    };
}

render_tests!(vm, fidget::vm::VmFunction);
render_tests!(vm3, fidget::vm::GenericVmFunction<3>);

#[cfg(feature = "jit")]
render_tests!(jit, fidget::jit::JitFunction);

/// Test generating `world_to_model` matrices from a `View2`
#[test]
fn test_camera_render_config() {
    let config = ImageRenderConfig {
        image_size: ImageSize::from(512),
        world_to_model: View2::from_center_and_scale(
            nalgebra::Vector2::new(0.5, 0.5),
            0.5,
        )
        .world_to_model(),
        ..Default::default()
    };
    let mat = config.mat();
    assert_eq!(
        mat.transform_point(&Point2::new(0.0, -1.0)),
        Point2::new(0.0, 1.0)
    );
    assert_eq!(
        mat.transform_point(&Point2::new(512.0, -1.0)),
        Point2::new(1.0, 1.0)
    );
    assert_eq!(
        mat.transform_point(&Point2::new(512.0, 511.0)),
        Point2::new(1.0, 0.0)
    );

    let config = ImageRenderConfig {
        image_size: ImageSize::from(512),
        world_to_model: View2::from_center_and_scale(
            nalgebra::Vector2::new(0.5, 0.5),
            0.25,
        )
        .world_to_model(),
        ..Default::default()
    };
    let mat = config.mat();
    assert_eq!(
        mat.transform_point(&Point2::new(0.0, -1.0)),
        Point2::new(0.25, 0.75)
    );
    assert_eq!(
        mat.transform_point(&Point2::new(512.0, -1.0)),
        Point2::new(0.75, 0.75)
    );
    assert_eq!(
        mat.transform_point(&Point2::new(512.0, 511.0)),
        Point2::new(0.75, 0.25)
    );
}
